package com.mvvm.start.base.base

import androidx.lifecycle.*
import com.mvvm.start.base.network.ApiException
import com.mvvm.start.base.network.NetworkRequestException
import com.mvvm.start.base.network.ServerErrorException
import kotlinx.coroutines.*
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

/**
 * @author lmy
 * @功能: ViewModel层的顶层封装
 * @Creat 2020/11/6 10:37 AM
 * @Compony 465008238@qq.com
 */
open class BaseViewModel : ViewModel() {
    var loadJobs = mutableListOf<Job>()

    /**
     * 这么写的好处是 _toast只在VM中被操作
     * toastText只被监听不允许外部更改
     */
    protected val _toast = MutableLiveData<String>()
    val toastText: LiveData<String>
        get() = _toast

    /**
     * 加载状态
     */
    protected val _isloading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean>
        get() = _isloading

    /**
     * 刷新状态
     */
    protected val _refresh = MutableLiveData<Boolean>()
    val refresh: LiveData<Boolean>
        get() = _refresh


    /**
     * 清楚加载框的Jobs
     */
    fun cancleLoadingJobs() {
        loadJobs.forEach {
            it.cancel()
        }
    }

    /**
     * 安全的发起网络请求
     * @param needToast Boolean 发生异常时是否需要弹出Toast
     * @param handlerException Function1<[@kotlin.ParameterName] String, Unit>? 异常回调
     * @param block SuspendFunction0<Unit> 发起网络请求的回调
     * @return Job
     */
    protected fun checkResult(
        needToast: Boolean = true,
        handlerException: ((errorMsg: String) -> Unit)? = null,
        block: suspend () -> Unit
    ) = viewModelScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, e ->
        /**
         * 使用协程的异常处理器来捕获网络请求中的异常
         */
        handlingExceptions(needToast, e)
        handlerException?.invoke(e.message.toString())
    }) {
        block.invoke()
    }

    /**
     * 安全的发起网络请求 针对于下拉刷新
     * @param needToast Boolean 发生异常时是否需要弹出Toast
     * @param handlerException Function1<[@kotlin.ParameterName] String, Unit>? 异常回调
     * @param block SuspendFunction0<Unit> 发起网络请求的回调
     * @return Job
     */
    protected fun checkResultRefresh(
        needToast: Boolean = true,
        handlerException: ((errorMsg: String) -> Unit)? = null,
        block: suspend () -> Unit
    ) = viewModelScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, e ->
        _refresh.postValue(false)
        /**
         * 使用协程的异常处理器来捕获网络请求中的异常
         */
        handlingExceptions(needToast, e)
        handlerException?.invoke(e.message.toString())
    }) {
        _refresh.postValue(true)
        block.invoke()
        _refresh.postValue(false)
    }

    /**
     * 安全的发起网络请求 针对于加载框的
     * @param needToast Boolean 发生异常时是否需要弹出Toast
     * @param handlerException Function1<[@kotlin.ParameterName] String, Unit>? 异常回调
     * @param block SuspendFunction0<Unit> 发起网络请求的回调
     * @return Job
     */
    protected fun checkResultLoad(
        needToast: Boolean = true,
        handlerException: ((errorMsg: String) -> Unit)? = null,
        block: suspend () -> Unit
    ) = loadJobs.add(viewModelScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, e ->
        _isloading.postValue(false)
        /**
         * 使用协程的异常处理器来捕获网络请求中的异常
         */
        handlingExceptions(needToast, e)
        handlerException?.invoke(e.message.toString())
    }) {
        _isloading.postValue(true)
        block.invoke()
        _isloading.postValue(false)
    })


    /**
     * 统一处理协程网络请求中的异常
     */
    private fun handlingExceptions(needToast: Boolean, e: Throwable) {
        //如果异常是取消协程的异常则不进行处理
        if (e !is CancellationException) {
            //已经处理过异常则直接return
            var apiException: ApiException =
                if (e is ServerErrorException && !e.isExceptionHandled) {
                    NetworkRequestException.instance.onError(e.dataResponse)
                    if (needToast) {
                        _toast.postValue(e.message)
                    }
                    return
                } else if (e is SocketTimeoutException) {
                    ApiException(ApiException.SOCKET_TIMEOUT_EXCEPTION, "连接超时,请检查您的网络")
                } else if (e is ConnectException) {
                    ApiException(ApiException.CONNECT_EXCEPTION, "无法连接,请检查您的网络")
                } else if (e is UnknownHostException) {
                    ApiException(ApiException.UNKNOWNHOST_EXCEPTION, "无法连接,请检查您的网络")
                } else if (e is HttpException) { //其他网络错误,统一处理网络错误
                    ApiException(e.code(), e.message)
                } else if (e is SecurityException) { //android代码执行时候权限异常，没有该权限执行
                    ApiException(ApiException.PERMISSION_DENY, e.message)
                } else { //其他未知错误
                    ApiException(ApiException.UNKNOWN, e.message)
                }
            NetworkRequestException.instance.onHttpError(apiException)
            if (needToast) {
                _toast.postValue(apiException.errorMsg)
            }
        }

    }
}



